﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Reflection;
using System.Collections;
using System.Windows.Forms;

namespace GameStudio.CodeGeneration
{
    public abstract class Sample
    {
        protected CodeCompileUnit targetUnit;
        protected CompilerParameters compilerParams;
        protected CodeTypeDeclaration targetClass;
        protected CodeConstructor constructor;
        protected CodeNamespace samples;
        protected string ClassName = "GeneratedObject";
        protected string NameSpace = "CodeSample";
        protected string FileName;
        protected string Path;
        protected string Suffix = ".cs";

        public Sample(string strFileName, string Path, string NameSpace)
        {
            FileName = strFileName;
            this.NameSpace = NameSpace;
            this.Path = System.IO.Path.Combine(Path, FileName);

        }

        public void AssignObjects()
        {
            constructor = new CodeConstructor();
            targetUnit = new CodeCompileUnit();
            compilerParams = new CompilerParameters();
            samples = new CodeNamespace(NameSpace);
            constructor.Attributes=MemberAttributes.Public;
        }

        public void Init()
        {
            compilerParams.OutputAssembly = FileName + ".dll";

            compilerParams.ReferencedAssemblies.Add("System.dll");
            //targetUnit.ReferencedAssemblies.Add("System.dll");

            samples.Imports.Add(new CodeNamespaceImport("System"));

            targetClass = new CodeTypeDeclaration(FileName);
            targetClass.IsClass = true;
            targetClass.TypeAttributes = TypeAttributes.Public;

            samples.Types.Add(targetClass);
            targetUnit.Namespaces.Add(samples);

        }

        public CodeMemberField AddFields(string FieldName, Type typy)
        {
            CodeMemberField _field = new CodeMemberField();
            _field.Attributes = MemberAttributes.Private;
            _field.Name = FieldName;
            _field.Type = new CodeTypeReference(typy);
            return _field;
        }

        public CodeMemberField AddFields(string FieldName, string type)
        {
            CodeMemberField _field = new CodeMemberField();
            _field.Attributes = MemberAttributes.Private;
            _field.Name = FieldName;
            _field.Type = new CodeTypeReference(type);
            return _field;
        }

        public void AddProperties(string PropertyName, string FieldOfGet, Type type)
        {
            CodeMemberProperty MyProperty = new CodeMemberProperty();
            MyProperty.Attributes =
                MemberAttributes.Public;
            MyProperty.Name = PropertyName;
            MyProperty.HasGet = true;
            //MyProperty.HasSet = true;
            MyProperty.Type = new CodeTypeReference(type);

            if (FieldOfGet != null)
            {
                MyProperty.GetStatements.Add(new CodeMethodReturnStatement(
                    new CodeFieldReferenceExpression(
                    new CodeThisReferenceExpression(), FieldOfGet)));
            }

            targetClass.Members.Add(MyProperty);


        }

        public void AddProperties(string PropertyName, string FieldOfGet, string Type)
        {
            CodeMemberProperty MyProperty = new CodeMemberProperty();
            MyProperty.Attributes =
                MemberAttributes.Public;
            MyProperty.Name = PropertyName;
            MyProperty.HasGet = true;
            //MyProperty.HasSet = true;
            MyProperty.Type = new CodeTypeReference(Type);

            if (FieldOfGet != null)
            {
                MyProperty.GetStatements.Add(new CodeMethodReturnStatement(
                    new CodeFieldReferenceExpression(
                    new CodeThisReferenceExpression(), FieldOfGet)));
            }

            targetClass.Members.Add(MyProperty);


        }

        public virtual CodeMemberMethod AddMethod(string MethodName, string[,] ArguAndFieldArr, string[] TypeOfArgu)
        {
            CodeMemberMethod MyMethod = new CodeMemberMethod();
            MyMethod.Attributes = MemberAttributes.Public;
            MyMethod.Name = MethodName;
            MyMethod.ReturnType =
                new CodeTypeReference(typeof(void));


            if (ArguAndFieldArr != null)
            {

                MyMethod.Parameters.AddRange(SetParameters(MyMethod, ArguAndFieldArr, TypeOfArgu));
            }

            return MyMethod;

        }

        private CodeParameterDeclarationExpressionCollection SetParameters(CodeMemberMethod MyMethod, string[,] ArguAndFieldArr, string[] TypeOfArgu)
        {
            CodeParameterDeclarationExpressionCollection parameters =
                new CodeParameterDeclarationExpressionCollection();

            int ndDim = ArguAndFieldArr.GetLength(1);
            for (int j = 0; j < ndDim; j++)
            {
                parameters.Add(new CodeParameterDeclarationExpression(
            TypeOfArgu[j], ArguAndFieldArr[1, j]));

                CodeFieldReferenceExpression widthReference =
                new CodeFieldReferenceExpression(
                new CodeThisReferenceExpression(), ArguAndFieldArr[0, j]);

                MyMethod.Statements.Add(new CodeAssignStatement(widthReference,
                    new CodeArgumentReferenceExpression(ArguAndFieldArr[1, j])));
            }
            return parameters;
        }


        public void AddConstructor(string[,] ArguAndFieldArr, string[] ArguTypes)
        {
            CodeConstructor constructor = new CodeConstructor();
            constructor.Attributes =
                MemberAttributes.Public;
            if (ArguAndFieldArr != null && ArguTypes != null)
            {
                constructor.Parameters.AddRange(SetParameters(constructor, ArguAndFieldArr, ArguTypes));
            }


        }

        public void AddParamConstructor(CodeParameterDeclarationExpressionCollection parameters)
        {

        }

        public CompilerResults GenerateCSharpCode()//string fileName
        {
            // Create the C# compiler
            CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
            //ICodeCompiler iCodeCompiler = provider.CreateCompiler();

            CodeGeneratorOptions options = new CodeGeneratorOptions();
            options.BracingStyle = "C";
            using (StreamWriter sourceWriter = new StreamWriter(Path + Suffix))
            {
                provider.GenerateCodeFromCompileUnit(
                    targetUnit, sourceWriter, options);
            }

            string compiledObject = ReadFile(Path + Suffix);
            //CompilerParameters cp = new CompilerParameters();

            // generate the DLL
            compilerParams.GenerateExecutable = false;
            //cp.GenerateInMemory = true;

            // Run the compiler and build the assembly
            CompilerResults result = provider.CompileAssemblyFromSource(compilerParams, compiledObject);

            // Load the generated assembly into the ApplicationDomain 
            Assembly asm = Assembly.LoadFrom(FileName + ".dll");

            if (File.Exists(Path + ".dll"))
            {
                File.Delete(Path + ".dll");
            }
            //File.Move(asm.Location, Path + ".dll");
            try
            {
                File.Move(asm.Location, Path + ".dll");
            }
            catch (Exception e)
            {
                MessageBox.Show(e.ToString());
            }

            return result;
        }

        private string ReadFile(string file)
        {
            return System.IO.File.ReadAllText(file);
        }

        public CodeVariableDeclarationStatement GenerateArr(string type, string name, int size)
        {
            CodeVariableDeclarationStatement cv1 = new CodeVariableDeclarationStatement(type + "[]", name,
                new CodeArrayCreateExpression(type, size));
            return cv1;
        }

        // Displays information from a CompilerResults.
        public static void DisplayCompilerResults(System.CodeDom.Compiler.CompilerResults cr)
        {
            // If errors occurred during compilation, output the compiler output and errors.
            if (cr.Errors.Count > 0)
            {
                //for (int i = 0; i < cr.Output.Count; i++)
                //    MessageBox.Show(cr.Output[i]);
                for (int i = 0; i < cr.Errors.Count; i++)
                    MessageBox.Show(i.ToString() + ": " + cr.Errors[i].ToString());

            }
            else
            {
                // Display information about the compiler's exit code and the generated assembly.
                Console.WriteLine("Compiler returned with result code: " + cr.NativeCompilerReturnValue.ToString());
                Console.WriteLine("Generated assembly name: " + cr.CompiledAssembly.FullName);
                if (cr.PathToAssembly == null)
                    Console.WriteLine("The assembly has been generated in memory.");
                else
                    Console.WriteLine("Path to assembly: " + cr.PathToAssembly);

                // Display temporary files information.
                if (!cr.TempFiles.KeepFiles)
                    Console.WriteLine("Temporary build files were deleted.");
                else
                {
                    Console.WriteLine("Temporary build files were not deleted.");
                    // Display a list of the temporary build files
                    IEnumerator enu = cr.TempFiles.GetEnumerator();
                    for (int i = 0; enu.MoveNext(); i++)
                        Console.WriteLine("TempFile " + i.ToString() + ": " + (string)enu.Current);
                }
            }
        }

        public void AddParentClass(string ParentClass)
        {
            targetClass.BaseTypes.Add(new CodeTypeReference(ParentClass));
        }
    }
}
